package io.github.wanggit.antrpc.commons.breaker;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.wanggit.antrpc.commons.config.CircuitBreakerConfig;
import io.github.wanggit.antrpc.commons.config.IConfiguration;
import io.vavr.CheckedFunction0;
import io.vavr.control.Try;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;

/** 熔断器配置容器。全局熔断器与接口级别熔断器的配置都保存在此容器中。 */
public class Resilience4jCircuitBreakerContainer implements IResilience4jCircuitBreakerContainer {

    private final ConcurrentHashMap<String, CircuitBreaker> breakers = new ConcurrentHashMap<>();
    private CircuitBreakerConfig globalConfig = null;
    private Map<String, CircuitBreakerConfig> interfaceConfigs = null;

    public Resilience4jCircuitBreakerContainer(IConfiguration configuration) {
        init(configuration);
    }

    private void init(IConfiguration configuration) {
        reInitGlobalConfig(configuration);
        reInitInterfaceConfig(configuration);
    }

    @Override
    public void reInitGlobalConfig(IConfiguration configuration) {
        if (null != configuration.getGlobalBreakerConfig()) {
            configuration.getGlobalBreakerConfig().checkSelf();
            globalConfig = configuration.getGlobalBreakerConfig();
        }
    }

    @Override
    public void reInitInterfaceConfig(IConfiguration configuration) {
        if (null != configuration.getInterfaceBreakerConfigs()) {
            configuration.getInterfaceBreakerConfigs().forEach((key, value) -> value.checkSelf());
            if (null == interfaceConfigs) {
                interfaceConfigs = configuration.getInterfaceBreakerConfigs();
            } else {
                interfaceConfigs.putAll(configuration.getInterfaceBreakerConfigs());
            }
        }
    }

    @Override
    public CircuitBreaker.State getState(String callUniqueKey) {
        CircuitBreaker circuitBreaker = breakers.get(callUniqueKey);
        if (null != circuitBreaker) {
            circuitBreaker.getState();
        }
        return null;
    }

    @Override
    public Object run(
            String interfaceName,
            String callUniqueKey,
            CheckedFunction0<Object> checkedFunction0,
            Consumer<? super Throwable> consumer,
            Function<? super Throwable, ?> onFailFunction) {
        CircuitBreakerConfig circuitBreakerConfig = getCircuitBreakerConfig(interfaceName);
        if (null == circuitBreakerConfig || !circuitBreakerConfig.isEnabled()) {
            return Try.of(checkedFunction0).onFailure(consumer).getOrElseGet(onFailFunction);
        } else {
            maybeCreateCircuitBreaker(callUniqueKey, circuitBreakerConfig);
        }
        return Try.of(breakers.get(callUniqueKey).decorateCheckedSupplier(checkedFunction0))
                .onFailure(consumer)
                .getOrElseGet(onFailFunction);
    }

    private void maybeCreateCircuitBreaker(
            String callUniqueKey, CircuitBreakerConfig circuitBreakerConfig) {
        if (!breakers.containsKey(callUniqueKey)) {
            synchronized (callUniqueKey.intern()) {
                if (!breakers.containsKey(callUniqueKey)) {
                    io.github.resilience4j.circuitbreaker.CircuitBreakerConfig config =
                            io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
                                    .failureRateThreshold(
                                            circuitBreakerConfig.getFailureRateThreshold())
                                    .permittedNumberOfCallsInHalfOpenState(
                                            circuitBreakerConfig.getPermittedNumOfCallsInHalfOpen())
                                    .build();
                    breakers.putIfAbsent(callUniqueKey, CircuitBreaker.of(callUniqueKey, config));
                }
            }
        }
    }

    @Override
    public CircuitBreakerConfig getCircuitBreakerConfig(String interfaceName) {
        CircuitBreakerConfig breakerConfig =
                null == interfaceConfigs ? null : interfaceConfigs.get(interfaceName);
        return null == breakerConfig ? globalConfig : breakerConfig;
    }
}
